package com.nulldev.util.manage.experiments;

import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.Map;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.util.random.GlobalRandom;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.data.Arrays.maps.trie.TrieMap;
import com.nulldev.util.internal.backport.concurrency9.Maps;
import com.nulldev.util.io.IOUtilsInternals;
import com.nulldev.util.io.IOUtilsOS;

public class Experiments {

	private static final Map<String, Experiment> EXPERIMENTS = new TrieMap<String, Experiment>();

	private static Experiment put(final Experiment e) {
		if (e == null)
			throw new NullPointerException("e == null!");
		return EXPERIMENTS.put(e.url(), e);
	}

	static {
		Arguments.awaitGet();
		if (EXPERIMENTS.isEmpty()) {
			/* nullUtil in General */
			put(new Experiment("nullUtil://use-trie-maps/", "NU_USE_TRIE_MAPS", "Uses TrieMaps instead of FastHashMaps", "nullUtil.use-trie-maps",
					"--nullUtil.useTrieMaps").setActive(Arguments.notArguments("--nullUtil.disableTrieMaps")));
			put(new Experiment("nullUtil://use-empty-do/", "NU_USE_EMPTY_DO", "Uses EmptyLists and EmptyMaps instead of immutable lists.",
					"nullUtil.use-empty-do", "--nullUtil.useEmptyDataObjects").setActive(Arguments.notArguments("--nullUtil.disableEDO")));
			put(new Experiment("nullUtil://use-glue-lists/", "NU_USE_GLUE_LISTS", "Uses GlueLists over FastArrayLists.", "nullUtil.use-glue-lists",
					"--nullUtil.enableGlueLists"));
			put(new Experiment("nullUtil://use-concurrent-lists/", "NU_USE_CONCURRENT_LISTS", "Uses ConcurrentLists over FastArrayLists.",
					"nullUtil.use-concurrent-lists", "--nullUtil.enableConcurrentLists"));
			put(new Experiment("nullUtil://use-concurrent-list-v2/", "NU_USE_CONCURRENT_LIST_V2", "Enables and uses an updated version of ConcurrentList.",
					"nullUtil.use-concurrent-list-v2", "--nullUtil.enableConcurrentListV2"));
			put(new Experiment("nullUtil://windows-use-utf8/", "NU_WINDOWS_FORCE_UTF8", "Uses UTF-8 over Windows' (questionable) encoding.",
					"nullutil.windows-force-utf8", "--nullUtil.forceUTF8").setActive(JVM.version() < 18f && IOUtilsOS.isWindows()));
			/* HttpServer */
			put(new Experiment("http-server://parallel-gzip/", "HTTP_PARALLEL_GZIP", "Uses multithreaded GZIP to improve performance.",
					"http-server.compression.parallel_gzip", "--HttpServer3.enableParallelGZip")
					.setActive(Arguments.notArguments("--HttpServer3.disableParallelGZip")));
			put(new Experiment("http-server://migz-for-gzip/", "HTTP_MIGZ_FOR_GZIP", "Uses MIGz for GZIP over Parallel/Standard.",
					"http-server.compression.gzip_migz", "--HttpServer3.useMIGz"));
			put(new Experiment("http-server://throughput-preference/", "SOCKET_THROUGHPUT", "Tells sockets to prefer throughput at the cost of latency.",
					"http-server.throughput-preferred", "--HttpServer3.preferThroughput"));
// REMOVED:	put(new Experiment("http-server://new-read-logic/", "NEW_READ_REQUEST_LOGIC", "Uses a faster request reading logic.",
//					"http-server.requests.new-logic", "--HttpServer3.useNewReadRequestLogic"));
			put(new Experiment("http-server://disable-no-delay/", "SERVER_TCP_NO_DELAY", "Disables TCP_NO_DELAY.", "http-server.disable-no-delay",
					"--HttpServer3.disableNoDelay"));
			put(new Experiment("http-server://enable-diag-reuse/", "ENABLE_DIAG_REUSE_SERVER", "(Deprecated) Allows broken pipes to be repaired.",
					"http-server.enable-diag-reuse", "--HttpServer3.enableDiagReuse"));
			put(new Experiment("http-server://use-async-submit/", "ENABLE_ASYNC_CLIENT_SUBMIT", "Handles clients via the async variant of EMAPI.",
					"http-server.use-async-submit", "--HttpServer3.enableAsyncSubmit"));
			put(new Experiment("http-server://queued-requests/", "ENABLE_QUEUED_REQUESTS", "Queues requests on a seperate thread.",
					"http-server.queued-requests", "--HttpServer3.enableQueuedRequests"));
			put(new Experiment("http-server://enable-ws/", "ENABLE_WS", "Enables support for the Web Socket protocol.", "http-server.web-sockets",
					"--HttpServer3.enableWebSockets"));
			put(new Experiment("http-server://enable-sse/", "ENABLE_SSE", "Enables support for the Server-Side Events protocol.", "http-server.sse-proto",
					"--HttpServer3.enableSSE"));
			put(new Experiment("http-server://use-trie-map/", "ENABLE_TRIE_MAPS_WS", "Uses TrieMaps over FastHashMaps.", "http-server.use-trie-maps",
					"--HttpServer3.enableTrieMap"));
			put(new Experiment("http-server://opt-out-floc/", "ENABLE_FLOC_OPT_OUT", "Opts out of Google's FLoC (enabled by default)",
					"http-server.opt-out-floc", "--HttpServer3.disableFLoC").setActive(Arguments.notArguments("--HttpServer3.enableFLoC")));
			put(new Experiment("http-server://early-hints/", "ENABLE_HTTP_103", "Enables RFC 8297 support to send preload hints.",
					"http-server.enable-early-hints", "--HttpServer3.enableEarlyHints"));
			put(new Experiment("http-server://post-response-header-flushing/", "ENABLE_HEADER_FLUSHING",
					"Enables flush() calls after the response header is sent.", "http-server.header-flushing", "--HttpServer3.enableHeaderFlushing"));
			put(new Experiment("http-server://response-reduce-flushing/", "REDUCE_IO_FLUSHING", "Reduces flush() calls throughout the response process.",
					"http-server.reduce-flushing", "--HttpServer3.enableReduceFlushing"));
			put(new Experiment("http-server://dynamic-buffer-sizes/", "DYNAMIC_OUTPUT_BUFFERS",
					"Allows larger buffers to be used in order to take advantage of window scaling.", "http-server.dynamic-buffer-sizes",
					"--HttpServer3.enableDynamicBufferSizing"));
			put(new Experiment("http-server://response-guess-content-length/", "ENABLE_CL_GUESSING", "Re-enables the Content-Length guessing logic.",
					"http-server.enable-content-length-guessing", "--HttpServer3.enableCLGuessing", "--HttpServer3.enableContentLengthGuessing"));
			put(new Experiment("http-server://enable-early-socket-close/", "ENABLE_EARLY_SOCK_CLOSE", "Enables closing the socket early when it is possible.",
					"http-server.enable-early-socket-close", "--HttpServer3.enableEarlySocketClose", "--HttpServer3.enableEarlySockClose"));
			/* IO [and Files] */
			put(new Experiment("io://enable-fast-copy/", "IO_FAST_COPY",
					"!!! UNDERGOING REWORK !!!\nUses NIO for all copy() operations on non-whitelisted environments.", "io.copy.use-nio",
					"--IOUtils.enableFastCopy", "--FileUtil.enableFastCopy"));
			put(new Experiment("io://nio-use-dma/", "IO_NIO_DMA_BUF", "NIO: Use DMA on non-whitelisted environments.", "io.copy.nio.use-dma",
					"--IOUtils.fastDMACopy", "--FileUtil.fastDMACopy"));
			put(new Experiment("io://nio-use-mmaps/", "IO_USE_MMAP", "Uses MMAP buffers for better peformance.", "io.copy.use-mmap", "--IOUtils.useMMAP"));
			/* OS-API */
			put(new Experiment("os://enable-new-detect-logic/", "OS_NEW_DETECT_WINDOWS_LOGIC",
					"Enables a more sane approach to checking if a user is an ADMIN.", "os.detection.new-logic", "--OS.enableNewDetectionLogic"));
			/* nTube */
			put(new Experiment("ntube://proxy-use-copyable/", "PROXY_API_USE_COPYABLE", "Uses the faster Copyable API.", "ntube.proxy.use-copyable",
					"--ProxyAPI.useCopyable").setActive(IOUtilsInternals.isWhitelistedForDMA()));
			put(new Experiment("ntube://defer-legacy-transport/", "INTERNAL_PROXY_NO_LEGACY", "Enables partial-HTTP/2 support in the internal proxy.",
					"ntube.internal-proxy.defer-legacy", "--NTubeServer.deferLegacyTransport").setActive(IOUtilsInternals.isWhitelistedForDMA()));
			put(new Experiment("ntube://proxy-use-modern-transport/", "PROXY_API_MODERN_TRANSPORT", "Enables full-HTTP/2 support in the proxy.",
					"ntube.proxy.modern-transport", "--ProxyAPI.enableModernTransport").setActive(IOUtilsInternals.isWhitelistedForDMA()));
			put(new Experiment("ntube://enable-late-extractions/", "VIDEO_API_ALLOW_LATE", "Re-enables late extraction support for nTube.",
					"ntube.video.enable-late", "--GetVideoPage.enableLateExtraction", "--NTubeServer.enableLateExtraction"));
			put(new Experiment("ntube://enable-fio-exceptions/", "NTUBE_FAST_IO_EXCEPTIONS", "Redirects uncaught exceptions to the FastIO API.",
					"ntube.redirect-to-fio", "--NTubeServer.enableFIOER").setActive(Arguments.notArguments("--NTubeServer.disableFIOER")));
			put(new Experiment("ntube://disable-reflective-warnings/", "NTUBE_NO_REFLECTIVE_WARNINGS", "Disables Java's reflection warnings.",
					"ntube.disable-reflection-warnings", "--NTubeServer.disableReflectionWarnings").setActive(JVM.version() >= 1.9f));
			put(new Experiment("ntube://redwood-captions-module/", "NTUBE_REDWOOD_CAPTIONS", "Enables captions on Redwood.", "ntube.redwood-captions-module",
					"--NTubeServer.enableRedwoodSubtitles").setActive(true));
			put(new Experiment("ntube://enable-dns-tweaks/", "NTUBE_DNS_TWEAKS", "Enables some internal DNS tweaks.", "ntube.dns-tweaks",
					"--NTubeServer.enableDNSTweaks").setActive(Arguments.notArguments("--NTubeServer.disableDNSTweaks")));
			put(new Experiment("ntube://use-shifter2-urlizer/", "NTUBE_URLIZER_SHIFT2", "Uses the incomplete Shifter2 cipher for IP protection.",
					"ntube.urlizer.shifter2", "--NTubeServer.urlizerShifter2", "--Urlizer.useShifter2"));
			put(new Experiment("ntube://enable-security-hardening/", "NTUBE_SECURITY_HARDENING", "Enables experimental security hardening for nTube.",
					"ntube.security.harden", "--NTubeServer.enableSecurityHardening"));
			put(new Experiment("ntube://use-content-range-on-all/", "NTUBE_USE_CR_ON_ALL_REQS",
					"Allows the Content-Range header to be used on generic requests.", "ntube.proxy.content-ranges", "--NTubeServer.useContentRangeOnAll"));
			put(new Experiment("ntube://okhttp-proxy-chunking/", "NTUBE_USE_OKHTTP_CHUNKING_PROXY", "Force-enables readAll chunking in OKHTTP.",
					"ntube.proxy.okhttp-chunking", "--NTubeServer.okHttpChunking"));
			put(new Experiment("ntube://parallel-stream-gen/", "NTUBE_USE_PARALLEL_STREAM_GEN", "Uses the new and improved stream generator.",
					"ntube.video.new-stream-gen", "--NTubeServer.parallelStreamGen", "--GetVideoPage.parallelStreamGen")
					.setActive(RANDOM_ENABLEMENT_PERCENTAGE(0.65f) /* 45% */));
			put(new Experiment("ntube://proxy-as-gateway/", "NTUBE_TREAT_PROXY_AS_GATEWAY", "Makes the proxy behave as a gateway to browsers.",
					"ntube.proxy.gateway-mode", "--NTubeServer.proxyAsGateway", "--ProxyAPI.asGateway"));
			put(new Experiment("ntube://proxy-enable-buffering/", "NTUBE_PROXY_USE_BUFFERING", "Enables buffering in proxy requests.",
					"ntube.proxy.enable-buffering", "--NTubeServer.enableProxyBuffering", "--ProxyAPI.useBuffering"));
			/* This probably is only useful with HTTP/3... */
			put(new Experiment("ntube://proxy-use-post/", "NTUBE_PROXY_USE_POST", "YouTube videos are requested using a POST method. Enabled by default.",
					"ntube.proxy.use-post", "--NTubeServer.proxyUsePOST", "--ProxyAPI.ytUsePOST")
					/* */
					.setActive(Arguments.notArguments("--NTubeServer.proxyUseGET", "--ProxyAPI.ytUseGET")));
			/* nTube (GVC) */
			put(new Experiment("ntube-gvc://enable-po-token/", "NTUBE_GVC_ENABLE_PO_TOKEN", "Enables sending the playbackObject token. Enabled by default",
					"ntube.gvc.send-po-token", "--GBC.enablePO").setActive(Arguments.notArguments("--GVC.disablePO")));
			put(new Experiment("ntube-gvc://enable-cver/", "NTUBE_GVC_ENABLE_CVER", "Enables sending the client version. Enabled by default",
					"ntube.gvc.send-cver", "--GVC.enableSendingCVER").setActive(Arguments.notArguments("--GVC.disableSendingCVER")));
			put(new Experiment("ntube-gvc://enable-rbuf/", "NTUBE_GVC_ENABLE_RBUF", "Enables sending a spoofed buffered video metric. Enabled by default",
					"ntube.gvc.send-rbuf", "--GVC.enableSendingRBUF").setActive(Arguments.notArguments("--GVC.disableSendingRBUF")));
			put(new Experiment("ntube-gvc://enable-ump/", "NTUBE_GVC_ENABLE_UMP", "Enables UMP policy behaviour.", "ntube.gvc.send-ump",
					"--GVC.enableSendingUMP"));
			put(new Experiment("ntube-gvc://enable-srfvp/", "NTUBE_GVC_ENABLE_SRFVP", "Enables SRFVP policy behaviour, enabling UMP will also enable this.",
					"ntube.gvc.send-srfvp", "--GVC.enableSendingSRFVP"));
			put(new Experiment("ntube-gvc://enable-request-number/", "NTUBE_GVC_ENABLE_RN",
					"Enables sending a request number count for a domain. Enabled by default", "ntube.gvc.send-rn", "--GVC.enableSendingRN")
					.setActive(Arguments.notArguments("--GVC.disableSendingRN")));
			put(new Experiment("ntube-gvc://gvc-use-fake-values/", "NTUBE_GVC_USE_FAKE_RBUF_VALUES",
					"Pretends to always have an empty video buffer. Requires RBUF to be enabled.", "ntube.gvc.fake-rbuf-values", "--GVC.useFakeRBUF"));
			put(new Experiment("ntube-gvc://enable-cpn-spoofing/", "NTUBE_GVC_SPOOF_CLIENT_PLAYBACK_NONCE",
					"Spoofs a nonce that YouTube uses to detect real clients. Enabled by default.", "ntube.gvc.enable-cpn", "--GVC.enableCPN")
					.setActive(Arguments.notArguments("--GVC.disableCPN")));
			/* Networking */
			put(new Experiment("rUDP://use-native-serialization/", "RUDP_NATIVE_NT_API", "Uses the native serialization provided by nullUtil.",
					"rudp.native-serialization", "--rUDP.useNativeSerialization"));
			/* Threading */
			put(new Experiment("thread-it://emapi-use-custom-def-service/", "EMAPI_CUSTOM_DEF_SERV", "Uses a non-standard ExecutorService for runtime.",
					"threadit.emapi.non-std-def-service", ExecutorManager.EXECUTOR_SERVICE_ARGS));
//			put(new Experiment("thread-it://emapi-use-custom-queue-backend/",
//					"EMAPI_CUSTOM_DEF_QUEUE", "Uses a non-standard queue for runtime.",
//					"threadit.emapi.non-std-queue", ExecutorManager.QUEUE_BACKEND_ARGS));
			put(new Experiment("thread-it://emapi-enable-batching/", "EMAPI_ENABLE_BATCHING", "Enables task batching during submission.",
					"threadit.emapi.batching", "--ExecutorManager.enableBatching").setActive(ExecutorManager.ENABLE_BATCH_OPERATIONS));
			/* one. */
			put(new Experiment("one://use-nitrous-rendering/", "ONE_USE_NITROUS", "Enables the slightly faster Nitrous API for Swing rendering.",
					"one.use-nitrous-render", "--one.useNitrous"));
			/* renderIt! */
			put(new Experiment("renderit://enable-experimental/", "RIT_USE_EXPERIMENTAL", "Enables experimental backends.", "renderit.enable-experimental",
					"--renderIt.enableExperimental"));
			/* microTLS */
			put(new Experiment("microtls://enable-extensions/", "MICROTLS_TLS_EXTENSIONS", "Enables TLS extensions.", "microtls.enable-extensions",
					"--microTLS.useExtensions").setActive(Arguments.notArguments("--microTLS.disableExtensions")));
			put(new Experiment("microtls://enable-compression/", "MICROTLS_TLS_COMPRESSION", "Enables ZLIB compression.", "microtls.enable-compression",
					"--microTLS.enableCompression"));
			put(new Experiment("microtls://enable-truncated-hmac/", "MICROTLS_TLS_TRUNCATED_HMAC", "Enables using a truncated HMAC.",
					"microtls.enable-truncated-hmac", "--microTLS.enableTruncatedHMAC"));
			/* Experiment debugging */
			if (Arguments.hasArgument("--Experiments.list", "--listExperiments")) {
				listAllExperiments();
			}
			if (get("nullUtil://windows-use-utf8/").isActive()) {
				System.setProperty("file.encoding", "UTF-8");
				try {
					final Field charset = Charset.class.getDeclaredField("defaultCharset");
					charset.setAccessible(true);
					charset.set(null, null);
				} catch (Exception e) {
				}
			}
		}
	}

	public static Map<String, Experiment> getAllExperiments() {
		return Maps.copyOf(EXPERIMENTS);
	}

	public static void listAllExperiments() {
		JVM.println("Listing ALL experiments:");
		for (final Map.Entry<String, Experiment> e : EXPERIMENTS.entrySet()) {
			JVM.println(e.getKey() + ": " + (e.getValue().isActive() ? "ACTIVE" : "INACTIVE"));
			JVM.println("\t" + e.getValue().description());
		}
	}

	public static void printActiveExperiments() {
		JVM.println("Active experiments:");
		for (final Map.Entry<String, Experiment> e : EXPERIMENTS.entrySet()) {
			if (e.getValue().isActive()) {
				JVM.println(e.getKey() + ": ACTIVE");
			}
		}
	}

	public static void invokeStatic() {
	}

	private static final Experiment STUB = new Experiment();

	private static boolean RANDOM_ENABLEMENT_PERCENTAGE(final float percentage) {
		return GlobalRandom.GLOBAL_FALLBACK_RANDOM.nextFloat() > percentage;
	}

	public static Experiment get(final String url) {
		if (url == null || url.isEmpty())
			throw new NullPointerException("Illegal url: " + url);
		return EXPERIMENTS.getOrDefault(url, STUB);
	}

	public static void main(String[] args) {
		Arguments.setup(new String[]
			{ "--Experiments.list", "--listExperiments" });
		listAllExperiments();
	}
}
